﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;

namespace Jage.Win32
{

    /// <summary>
    /// Generically adds data to a listview in Details mode.
    /// </summary>
    public class ListViewDatabinder<T>
        where T : class
    {

        private ListView _listView;
        private PropertyInfo _primaryKey;

        public ListViewDatabinder(ListView listView, string primaryKey)
        {
            ColumnDefinitions = new List<ListViewColumnDefinition<T>>();
            _primaryKey = typeof(T).GetProperty(primaryKey);
            if (_primaryKey == null) throw new ArgumentException(String.Format("PrimaryKey property \"{0}\" does not exist on \"{1}\".", primaryKey, typeof(T).ToString()));
            _listView = listView;
        }

        public void Databind(IEnumerable<T> data)
        {
            if (_listView.View == View.Details)
            {
                if (_listView.Columns.Count == ColumnDefinitions.Count)
                {
                    var selectedIds = from s in SelectedObjects select (Guid)_primaryKey.GetValue(s, null); // save selection to restore it after refresh
                    _listView.BeginUpdate();
                    try
                    {
                        _listView.Items.Clear();
                        foreach (var obj in data)
                        {
                            var item = AddRow(obj);
                            item.Selected = selectedIds.Contains((Guid)_primaryKey.GetValue(obj, null)); // restore selection state
                        }
                    }
                    finally
                    {
                        _listView.EndUpdate();
                    }
                }
                else
                {
                    throw new ApplicationException(String.Format("Cannot bind to ListView because it has a different number of columns ({0}) than the columns in this ListViewBinder ({1}).", _listView.Columns.Count, ColumnDefinitions.Count));
                }
            }
            else
            {
                throw new ApplicationException("Cannot bind to ListView because it is not in Details mode.");
            }
        }

        public IEnumerable<T> AllObjects
        {
            get
            {
                List<T> list = new List<T>();
                foreach (ListViewItem item in _listView.Items)
                {
                    list.Add(item.Tag as T);
                }
                return list;
            }
        }

        public IEnumerable<Guid> AllIds
        {
            get
            {
                return Array.ConvertAll(AllObjects.ToArray(), o => (Guid)_primaryKey.GetValue(o, null));
            }
        }

        /// <summary>
        /// Gets or sets the currently selected objects.
        /// </summary>
        public IEnumerable<T> SelectedObjects
        {
            get
            {
                List<T> list = new List<T>();
                foreach (ListViewItem item in _listView.SelectedItems)
                {
                    list.Add(item.Tag as T);
                }
                return list;
            }
            set
            {
                foreach (ListViewItem item in _listView.Items)
                {
                    item.Selected = value.Contains(item.Tag);
                }
            }
        }

        public IEnumerable<Guid> SelectedIds
        {
            get
            {
                return Array.ConvertAll(SelectedObjects.ToArray(), o => (Guid)_primaryKey.GetValue(o, null));
            }
        }

        public int ObjectCount
        {
            get { return _listView.Items.Count; }
        }

        private ListViewItem AddRow(object obj)
        {
            bool firstColumn = true;
            ListViewItem item = null;
            foreach (var columnDefinition in ColumnDefinitions)
            {
                if (firstColumn)
                {
                    item = _listView.Items.Add(columnDefinition.GetDisplayText(obj));
                    item.Tag = obj;
                    firstColumn = false;
                }
                else
                {
                    item.SubItems.Add(columnDefinition.GetDisplayText(obj));
                }
            }
            return item;
        }

        private List<ListViewColumnDefinition<T>> ColumnDefinitions { get; set; }


        public void Clear()
        {
            _listView.Items.Clear();
        }

        public void AddColumnDefinition(string propertyName)
        {
            ColumnDefinitions.Add(new ListViewColumnDefinition<T>(propertyName));
        }

        public void AddColumnDefinition(Func<object[], string> formatter, params string[] propertyNames)
        {
            ColumnDefinitions.Add(new ListViewColumnDefinition<T>(formatter, propertyNames));
        }

        public void SelectAll()
        {
            int count = _listView.Items.Count;
            if (count > 200) BusyBox.ShowBusy("Selecting...");
            _listView.BeginUpdate();
            try
            {
                for (int i = 0; i < count; i++)
                {
                    _listView.SelectedIndices.Add(i);
                }
            }
            finally
            {
                _listView.EndUpdate();
                if (count > 200) BusyBox.HideBusy();
            }
        }
    }

    /// <summary>
    /// Defines one column in a listview.
    /// </summary>
    public class ListViewColumnDefinition<T>
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="propertyName">Name of the property in the data object</param>
        /// <param name="width">Column width in pixels</param>
        public ListViewColumnDefinition(string propertyName)
            : this((o) => o[0] != null ? o[0].ToString() : String.Empty, propertyName)
        {
        }

        /// <summary>
        /// Defines a column that uses any number of properties of the object.  The formatter method uses the values of the properties to create the display text.
        /// </summary>
        public ListViewColumnDefinition(Func<object[], string> formatter, params string[] propertyNames)
        {
            PropertyNames = propertyNames;
            MultiFormatter = formatter;
        }

        public string GetDisplayText(object obj)
        {
            if (PropertyNames != null)
            {
                List<object> values = new List<Object>();
                foreach (string propertyName in PropertyNames)
                {
                    var prop = obj.GetType().GetProperty(propertyName);
                    if (prop != null)
                    {
                        values.Add(prop.GetValue(obj, null));
                    }
                    else
                    {
                        return string.Format("<ERR:{0}>", propertyName);
                    }
                }
                return MultiFormatter.Invoke(values.ToArray());
            }
            else
            {
                throw new Exception("No PropertyNames defined.");
            }
        }

        public string[] PropertyNames { get; private set; }
        
        public Func<object[], string> MultiFormatter { get; private set; }

    }

}
